#include "../envMap.hlsl"

/*
contributors:  The Art Of Code
description: |
    Raymarching for glass render. For more info, see the video below link:
    Tutorial 1: https://youtu.be/NCpaaLkmXI8
    Tutorial 2: https://youtu.be/0RWaR7zApEo
use: <float3> raymarchGlass( in <float3> ray, in <float3> pos, in <float> ior, in <float> roughness ) 
options:
    - RAYMARCH_GLASS_DENSITY: 0.                        [Density of the ray going through the glass]
    - RAYMARCH_GLASS_COLOR: float3(1.0, 1.0, 1.0)       [Color of the glass]
    - RAYMARCH_GLASS_WAVELENGTH                         [Define this option to enable chromatic abberation effects]
    - RAYMARCH_GLASS_ENABLE_REFLECTION                  [Define this option to enable reflection]
    - RAYMARCH_GLASS_REFLECTION_EFFECT 5.               [The higher the value, the less reflections area from surface view]
    - RAYMARCH_GLASS_CHROMATIC_ABBERATION .01           [Chromatic Abberation Effects on environment map]
    - RAYMARCH_GLASS_EDGE_SHARPNESS                     [Optional, to determine the edge sharpness]
    - RAYMARCH_GLASS_FNC_MANUAL                         [Optional, enable this to set glass params manually without using defines]
    - RAYMARCH_GLASS_FNC(RAY,POSITION,IOR,ROUGHNESS)
    - RAYMARCH_GLASS_MAP_FNC(res, rdIn, rdOut, pEnter, pExit, nEnter, nExit, ior, roughness)
*/

#ifndef RAYMARCH_GLASS_DENSITY
#define RAYMARCH_GLASS_DENSITY 0.
#endif
#ifndef RAYMARCH_GLASS_COLOR
#define RAYMARCH_GLASS_COLOR float3(1.,1.,1.)
#endif

#if !defined(RAYMARCH_GLASS_REFLECTION_EFFECT) && defined(RAYMARCH_GLASS_ENABLE_REFLECTION)
#define RAYMARCH_GLASS_REFLECTION_EFFECT 5.
#endif

#ifdef RAYMARCH_GLASS_MAP_FNC
#define RAYMARCH_GLASS_WAVELENGTH_MAP_FNC(res, rdIn, rdOut, pEnter, pExit, nEnter, nExit, ior, roughness) RAYMARCH_GLASS_MAP_FNC(res, rdIn, rdOut, pEnter, pExit, nEnter, nExit, ior, roughness)
#endif

#ifndef RAYMARCH_GLASS_FNC
#define RAYMARCH_GLASS_FNC(RAY, POSITION, IOR, ROUGHNESS) raymarchDefaultGlass(RAY, POSITION, IOR, ROUGHNESS)
#endif

#ifndef RAYMARCH_GLASS_CHROMATIC_ABBERATION
#define RAYMARCH_GLASS_CHROMATIC_ABBERATION .01
#endif

#ifndef RAYMARCH_GLASS_SAMPLES
#define RAYMARCH_GLASS_SAMPLES 50
#endif

#ifndef RAYMARCH_GLASS_MIN_DIST
#define RAYMARCH_GLASS_MIN_DIST 0.
#endif

#ifndef RAYMARCH_GLASS_MAX_DIST
#define RAYMARCH_GLASS_MAX_DIST 100.
#endif

#ifndef RAYMARCH_GLASS_MIN_HIT_DIST
#define RAYMARCH_GLASS_MIN_HIT_DIST .0001
#endif

#ifndef RAYMARCH_MAP_DISTANCE
#define RAYMARCH_MAP_DISTANCE a
#endif

#ifndef RAYMARCH_MAP_FNC
#define RAYMARCH_MAP_FNC(POS) raymarchMap(POS)
#endif

#ifndef RAYMARCH_MAP_TYPE
#define RAYMARCH_MAP_TYPE float4
#endif

#ifndef RAYMARCH_MAP_MATERIAL_TYPE
#define RAYMARCH_MAP_MATERIAL_TYPE float3
#endif

#ifndef RAYMARCH_GLASS_MAP_MATERIAL
#define RAYMARCH_GLASS_MAP_MATERIAL rgb
#endif

#ifndef FNC_RAYMARCH_GLASS
#define FNC_RAYMARCH_GLASS
RAYMARCH_MAP_TYPE raymarchGlassMarching(in float3 ro, in float3 rd) {
    float tmin = RAYMARCH_GLASS_MIN_DIST;
    float tmax = RAYMARCH_GLASS_MAX_DIST;

    float t = tmin;
    RAYMARCH_MAP_MATERIAL_TYPE m;

    // Because when the ray is inside the surface,the distance becomes negative.
    float side = sign(RAYMARCH_MAP_FNC(ro).RAYMARCH_MAP_DISTANCE);

    for (int i = 0; i < RAYMARCH_GLASS_SAMPLES; i++) {
        float3 pos = ro + rd * t;
        RAYMARCH_MAP_TYPE sideDirection = RAYMARCH_MAP_FNC(pos);
        t += sideDirection.RAYMARCH_MAP_DISTANCE * side;
        m = sideDirection.RAYMARCH_GLASS_MAP_MATERIAL;
        if(t > tmax || abs(t) < RAYMARCH_GLASS_MIN_HIT_DIST)
            break;

    }
    return RAYMARCH_MAP_TYPE(m, t);
}
#endif

#ifndef FNC_RAYMARCH_DEFAULT_GLASS
#define FNC_RAYMARCH_DEFAULT_GLASS

// For overwriting the parameters rendering that can be set manually
#ifdef RAYMARCH_GLASS_FNC_MANUAL
float3 raymarchDefaultGlass(in float3 ray, in float3 pos, in float ior, in float roughness, in float glassSharpness, in float chromatic, in float density, in bool enableReflection, in float reflection, in float3 colorGlass) {
    float3 color = float3(0.);

    RAYMARCH_MAP_TYPE marchOutside = raymarchGlassMarching(pos,ray); // Outside of the object
    if(marchOutside.RAYMARCH_MAP_DISTANCE < RAYMARCH_MAX_DIST) {
        float3 newPos = pos + ray * marchOutside.RAYMARCH_MAP_DISTANCE;
        float3 nEnter, nExit;

        nEnter = raymarchNormal(newPos, glassSharpness);

        float3 newReflect = reflect(ray, nEnter);

        color = envMap(newReflect, roughness).rgb;

        float3 rdIn = refract(ray, nEnter, 1./ior);
        float3 pEnter = newPos - nEnter * RAYMARCH_GLASS_MIN_HIT_DIST * 3.;
        
        RAYMARCH_MAP_TYPE marchInside = raymarchGlassMarching(pEnter, rdIn); // Inside the object
        
        float3 pExit = pEnter + rdIn * marchInside.RAYMARCH_MAP_DISTANCE;

        nExit = -raymarchNormal(pExit, glassSharpness);

        float3 rdOut, res;
        if(chromatic != 0.) {

            #ifdef RAYMARCH_GLASS_WAVELENGTH_MAP_FNC
                RAYMARCH_GLASS_WAVELENGTH_MAP_FNC(res, rdIn, rdOut, pEnter, pExit, nEnter, nExit, ior, roughness);
            #else
                // Red
                rdOut = refract(rdIn, nExit, ior - chromatic);

                if(dot(rdOut, rdOut) == 0.)
                    rdOut = reflect(rdIn, nExit);

                res.r = envMap(rdOut, roughness).r;

                // Green
                rdOut = refract(rdIn, nExit, ior);

                if(dot(rdOut, rdOut) == 0.)
                    rdOut = reflect(rdIn, nExit);

                res.g = envMap(rdOut, roughness).g;

                // Blue
                rdOut = refract(rdIn, nExit, ior + chromatic);

                if(dot(rdOut, rdOut) == 0.)
                    rdOut = reflect(rdIn, nExit);

                res.b = envMap(rdOut, roughness).b;
            #endif

            float optDist = exp(-marchInside.RAYMARCH_MAP_DISTANCE * density);

            res *= optDist * colorGlass;
            
            if (enableReflection) {
                float fresnelVal = pow(1.+dot(ray, nEnter), reflection);
                return lerp(res, color, saturate(fresnelVal));
            } else {
                return res;
            }
        } else {
            rdOut = refract(rdIn, nExit, ior);

            if(dot(rdOut, rdOut) == 0.)
                rdOut = reflect(rdIn, nExit);

            float optDist = exp(-marchInside.RAYMARCH_MAP_DISTANCE * density);

            res = envMap(rdOut, roughness).rgb;

            res *= optDist * colorGlass;

            if (enableReflection) {
                float fresnelVal = pow(1.+dot(ray, nEnter), reflection);
                return lerp(res, color, saturate(fresnelVal));
            } else {
                return res;
            }
        }
    } else {
        return envMap(ray, 0.).rgb;
    }
}
#endif

float3 raymarchDefaultGlass(in float3 ray, in float3 pos, in float ior, in float roughness) {
    float3 color = float3(0.);

    RAYMARCH_MAP_TYPE marchOutside = raymarchGlassMarching(pos,ray); // Outside of the object
    if(marchOutside.RAYMARCH_MAP_DISTANCE < RAYMARCH_MAX_DIST) {
        float3 newPos = pos + ray * marchOutside.RAYMARCH_MAP_DISTANCE;
        float3 nEnter, nExit;

    #ifdef RAYMARCH_GLASS_EDGE_SHARPNESS
        nEnter = raymarchNormal(newPos, RAYMARCH_GLASS_EDGE_SHARPNESS);
    #else
        nEnter = raymarchNormal(newPos);
    #endif
        float3 newReflect = reflect(ray, nEnter);

        color = envMap(newReflect, roughness).rgb

        float3 rdIn = refract(ray, nEnter, 1./ior);
        float3 pEnter = newPos - nEnter * RAYMARCH_GLASS_MIN_HIT_DIST * 3.;
        
        RAYMARCH_MAP_TYPE marchInside = raymarchGlassMarching(pEnter, rdIn); // Inside the object
        
        float3 pExit = pEnter + rdIn * marchInside.RAYMARCH_MAP_DISTANCE;

    #ifdef RAYMARCH_GLASS_EDGE_SHARPNESS
        nExit = -raymarchNormal(pExit, RAYMARCH_GLASS_EDGE_SHARPNESS);
    #else
        nExit = -raymarchNormal(pExit);
    #endif

        float3 rdOut, res;
    #ifdef RAYMARCH_GLASS_WAVELENGTH
        float NoV = dot(ray, nEnter);

        #ifdef RAYMARCH_GLASS_WAVELENGTH_MAP_FNC
            RAYMARCH_GLASS_WAVELENGTH_MAP_FNC(res, rdIn, rdOut, pEnter, pExit, nEnter, nExit, ior, roughness);
        #else
            // Red
            rdOut = refract(rdIn, nExit, ior - RAYMARCH_GLASS_CHROMATIC_ABBERATION);

            if(dot(rdOut, rdOut) == 0.)
                rdOut = reflect(rdIn, nExit);

            res.r = envMap(rdOut, roughness).r;

            // Green
            rdOut = refract(rdIn, nExit, ior);

            if(dot(rdOut, rdOut) == 0.)
                rdOut = reflect(rdIn, nExit);

            res.g = envMap(rdOut, roughness).g;

            // Blue
            rdOut = refract(rdIn, nExit, ior + RAYMARCH_GLASS_CHROMATIC_ABBERATION);

            if(dot(rdOut, rdOut) == 0.)
                rdOut = reflect(rdIn, nExit);

            res.b = envMap(rdOut, roughness).b;
        #endif

        float optDist = exp(-marchInside.RAYMARCH_MAP_DISTANCE * RAYMARCH_GLASS_DENSITY);

        res *= optDist * RAYMARCH_GLASS_COLOR;

        #ifdef RAYMARCH_GLASS_ENABLE_REFLECTION
            float fresnelVal = pow(1.+dot(ray, nEnter), RAYMARCH_GLASS_REFLECTION_EFFECT);
            return lerp(res, color, saturate(fresnelVal));
        #else
            return res;
        #endif
    #else
        rdOut = refract(rdIn, nExit, ior);

        if(dot(rdOut, rdOut) == 0.)
            rdOut = reflect(rdIn, nExit);

        float optDist = exp(-marchInside.RAYMARCH_MAP_DISTANCE * RAYMARCH_GLASS_DENSITY);

        res = envMap(rdOut, roughness).rgb;

        res *= optDist * RAYMARCH_GLASS_COLOR;

        #ifdef RAYMARCH_GLASS_ENABLE_REFLECTION
            float fresnelVal = pow(1.+dot(ray, nEnter), RAYMARCH_GLASS_REFLECTION_EFFECT);
            return lerp(res, color, saturate(fresnelVal));
        #else
            return res;
        #endif
    #endif
    } else {
        return envMap(ray, 0.).rgb;
    }
}

float3 raymarchGlass(in float3 ray, in float3 pos, in float ior, in float roughness) {
    return RAYMARCH_GLASS_FNC(ray, pos, ior, roughness);
}
#endif